<?php
/**
 * Author: DefinitlyEvil
 * Created at: 2020/4/15 18:01
 */

namespace App\PublicAPI;


use App\Controller;
use App\Tools\ECSTools;
use App\Tools\Encryption;
use App\Tools\GameModes;
use App\Tools\ImageCode;
use App\Tools\Pattern;

class RoomController extends Controller
{

    const CREATE_COOLDOWN = 15 * 60;
    const CONNECT_COOLDOWN = 3;

    public static function register(\FastRoute\RouteCollector $r){
        $r->post('/maps', [self::class, 'maps']);
        $r->post('/list', [self::class, 'listRooms']);
        $r->post('/create', [self::class, 'create']);
        $r->post('/members', [self::class, 'members']);
        $r->post('/connect', [self::class, 'connect']);
    }

    public function maps($params = []) {
        $data = json_decode(file_get_contents('../maps.json'), true);
        if(!isset($params['gm']) or !Pattern::matches('/^[A-Z]{1,50}$/', $params['gm'])) {
            return [$data, true];
        } else {
            if(isset( $data[$params['gm']] )) {
                return [[$params['gm'] => $data[$params['gm']]], true];
            } else {
                return [[$params['gm'] => []], true];
            }
        }
    }

    public function create($params = []) {

        // ONLY ALLOW ME TO CREATE NOW!
        if($this->user['donated'] !== true) {
            return ['测试期间只有赞助了的玩家可以创建房间! ', false];
        }

        if(!isset($params['t']) or !Pattern::matches(Pattern::TITLE, $params['t']) or !is_string($params['t']) or !isset($params['map']) or !isset($params['gm']) or !Pattern::matches('/^[A-Z]{1,50}$/', $params['gm']) or !in_array($params['gm'], GameModes::ALL, true) or !isset($params['ci']) or !Pattern::matches(Pattern::IMAGE_CODE_ID, $params['ci']) or !isset($params['c']) or !is_string($params['c']) or !Pattern::matches(Pattern::IMAGE_CODE_VALUE, $params['c']) or !Pattern::matches('/^[A-Za-z0-9_\-]+$/', $params['map'])) {
            return ['invalid params', false];
        }

        $redis_cooldown_key = sprintf('create-cooldown:%d', $this->user['id']);
        {
            $cooldown_start = $this->getRedis()->get($redis_cooldown_key);
            if($cooldown_start !== false) {
                $cooldown_start = intval($cooldown_start);
                return [sprintf('cooldown left %d secs', $cooldown_start+self::CREATE_COOLDOWN-time()), false];
            }
        }

        $gm = &$params['gm'];
        $map = &$params['map'];

        if(!GameModes::doesMapExist($gm, $map)) {
            return ['map does not exist', false];
        }

        $title = &$params['t'];
        $ci = &$params['ci'];
        $c = &$params['c'];

        if(!ImageCode::verify($this->getRedis(), $ci, $c)) {
            return ['invalid captcha code', false];
        }

        try {
            $ecsInstanceId = ECSTools::createEcs($gm, $map);
        } catch (\Exception $ex) {
            return ['room create failed: ' . $ex->getMessage(), false];
        }

        $this->getRedis()->setex($redis_cooldown_key, self::CREATE_COOLDOWN, sprintf('%d', time()));

        // create ECS
        // TODO: $ecsInstanceId = xxxxx;
        // $ecsInstanceId = "ecs-fake-" . time();
        $connectAddress = '';
        $connectPort = 25566;
        $createTime = time();
        $expireTime = time() + ECSTools::EXPIRE_TIME;

        // insert into database
        $stm = $this->getPdo()->prepare('INSERT INTO `rooms` (`created_by`,`create_time`,`expire_time`,`ecs_instance_id`,`title`,`gamemode`,`map`,`connect_address`,`connect_port`) VALUES(:cby,:ctime,:etime,:ecs,:title,:gm,:map,:addr,:port)');
        $stm->bindParam(':cby', $this->user['id'], \PDO::PARAM_INT);
        $stm->bindParam(':ctime', $createTime);
        $stm->bindParam(':etime', $expireTime);
        $stm->bindParam(':ecs', $ecsInstanceId);
        $stm->bindParam(':title', $title);
        $stm->bindParam(':gm', $gm);
        $stm->bindParam(':map', $map);
        $stm->bindParam(':addr', $connectAddress);
        $stm->bindParam(':port', $connectPort);
        if($stm->execute() === false or $stm->rowCount() <= 0) {
            // delete instance
            ECSTools::delete($ecsInstanceId);
            return ['room save error / 1', false];
        }

        return [['room' => intval($this->getPdo()->lastInsertId())], true];
    }

    public function listRooms($params = []) {
        $stm = $this->getPdo()->prepare(
            'SELECT `id`,`title`,`gamemode`,`map`,`connect_port`,`players_online`,`players_max` FROM `rooms` WHERE `expire_time`>:t'
        );
        $current_time = time();
        $stm->bindParam(':t', $current_time);
        if($stm->execute() === false) return ['database error / 1', false];

        $results = $stm->fetchAll(\PDO::FETCH_ASSOC);
        if($results === false) return ['database error / 2', false];

        return [$results, true];
    }

    public function members($params = []) {
        if(!isset($params['r']) or !is_int($params['r'])) {
            return ['invalid room', false];
        }

        $time = time();

        $pdo = $this->getPdo();
        $stm = $pdo->prepare(<<< EOL
SELECT
  `account_online`.`account_id`,
  `accounts`.`username`,
  `accounts`.`uuid`
FROM `account_online`
LEFT JOIN `accounts` ON `accounts`.`id`=`account_online`.`account_id`
WHERE `account_online`.`room_id`=:r AND `account_online`.`last_seen`>(:t-60)
EOL
);
        $stm->bindParam(':r', $params['r'], \PDO::PARAM_INT);
        $stm->bindParam(':t', $time, \PDO::PARAM_INT);
        if($stm->execute() === false) return ['db err / 1', false];
        $results = $stm->fetchAll(\PDO::FETCH_ASSOC);
        if($results === false) return ['db err / 2', false];
        return [['members' => $results], true];
    }

    public function connect($params = []) {
        if(!isset($params['r']) or !is_int($params['r'])) {
            return ['invalid room', false];
        }

        $cooldown_key = sprintf('connect-cooldown:%d', $this->user['id']);
        $cooldown_start = $this->getRedis()->get($cooldown_key);
        if($cooldown_start !== false) {
            $cooldown_start = intval($cooldown_start);
            return [sprintf('connect cooldown %d secs', $cooldown_start+self::CONNECT_COOLDOWN-time()), false];
        }

        $stm = $this->getPdo()->prepare('SELECT `id`,`ecs_instance_id`,`connect_address`,`connect_port` FROM `rooms` WHERE `id`=:r AND `expire_time`>UNIX_TIMESTAMP()');
        $stm->bindParam(':r', $params['r']);
        if($stm->execute() === false) return ['database error', false];

        $result = $stm->fetch(\PDO::FETCH_ASSOC);

        if($result === false) return ['room not found', false];

        if(strlen($result['connect_address']) <= 0) return ['room not ready', false];

        $this->getRedis()->setex($cooldown_key, self::CONNECT_COOLDOWN, sprintf('%d', time()));

        $token = json_encode([
            "user" => $this->user['id'],
            "server" => $result['ecs_instance_id'],
            "expire" => (time() + 10)
        ]);
        $token_enc = Encryption::encrypt($token);
        $token_enc = base64_encode($token_enc[0]) . ":" . base64_encode($token_enc[1]);

        return [
            [
                'id' => $result['id'],
                'address' => $result['connect_address'],
                'port' => $result['connect_port'],
                'server' => $result['ecs_instance_id'],
                'token' => $token_enc
            ],
            true
        ];
    }
}